本节翻译自
综述:在本节中,你将学会如何使用特质;以及抽象类型、自身类型和复合类型这几个高级类型。
特质
特质用于在类之间共享接口和字段。它们类似于Java 8的接口。类和对象可以扩展特征,但是特质不能被实例化,因此没有参数。
定义特质
一个最小的特质就是关键字 trait
和标识符:
trait HairColor
作为泛型类型和抽象方法,特质显得特别有用。
trait Iterator[A] {
def hasNext: Boolean
def next(): A
}
扩展 trait Iterator[A]
需要类型 A
还有方法 hasNext
和 next
的实现。
使用特质
使用关键字 extends
可以扩展特质。然后使用关键字 override
来实现该特质里的任何抽象成员。
class IntIterator(to: Int) extends Iterator[Int] {
private var current = 0
override def hasNext: Boolean = current < to
override def next(): Int = {
if (hasNext) {
val t = current
current += 1
t
} else 0
}
}
val iterator = new IntIterator(10)
iterator.next() // prints 0
iterator.next() // prints 1
这个 IntIterator
类将一个参数 to
作为一个上界。它扩展了 Iterator[Int]
,这意味着方法 next
必须返回一个 Int。
子类型
可以在需要特征的地方使用子类型。
import scala.collection.mutable.ArrayBuffer
trait Pet {
val name: String
}
class Cat(val name: String) extends Pet
class Dog(val name: String) extends Pet
val dog = new Dog("Harry")
val cat = new Cat("Sally")
val animals = ArrayBuffer.empty[Pet]
animals.append(dog)
animals.append(cat)
animals.foreach(pet => println(pet.name)) // Prints Harry Sally
trait Pet
有一个抽象的字段 name
,它在构造函数中由 Cat
和 Dog
实现。在最后一行,我们调用 pet.name
,该名称必须在特质 Pet
的任何子类型中实现。
抽象类型
特质和抽象类可以有一个抽象类型的成员。这意味着具体的实现定义了实际的类型。这里有一个例子:
trait Buffer {
type T
val element: T
}
这里我们定义了一个抽象类型 type T
。它被用来描述 element
。我们可以在抽象类中扩展这个特性,并向 T
添加一个上类型边界绑定,以使其更具体。
abstract class SeqBuffer extends Buffer {
type U
type T <: Seq[U]
def length = element.length
}
请注意我们是如何使用另一个抽象类型 type U
作为一个上类型绑定:这个 SeqBuffer
类允许我们只在 Buffer 中存储序列,它声明 type T
必须是 Seq U
的子类型,用于新的抽象类型 U
。
带有抽象类型成员的特质或类经常与匿名类实例化相结合使用。为了说明这一点,我们现在来看一个程序,它处理一个 SeqBuffer,引用了一个 Int 列表:
abstract class IntSeqBuffer extends SeqBuffer {
type U = Int
}
def newIntSeqBuf(elem1: Int, elem2: Int): IntSeqBuffer =
new IntSeqBuffer {
type T = List[U]
val element = List(elem1, elem2)
}
val buf = newIntSeqBuf(7, 8)
println("length = " + buf.length)
println("content = " + buf.element)
这里有一个使用匿名类 IntSeqBuf
实现的工厂 newIntSeqBuf
(即 new IntSeqBuffer
),它将 type T
设置为一个 List[Int]
。
也可以将抽象类型成员转换为类的类型参数,反之亦然。下面是上述代码的一个版本,它只使用类型参数:
abstract class Buffer[+T] {
val element: T
}
abstract class SeqBuffer[U, +T <: Seq[U]] extends Buffer[T] {
def length = element.length
}
def newIntSeqBuf(e1: Int, e2: Int): SeqBuffer[Int, Seq[Int]] =
new SeqBuffer[Int, List[Int]] {
val element = List(e1, e2)
}
val buf = newIntSeqBuf(7, 8)
println("length = " + buf.length)
println("content = " + buf.element)
注意,我们必须在这里使用型变注解(+T <: Seq[U]
),以隐藏从方法 newIntSeqBuf
返回的对象的具体序列实现类型。此外,有些情况下,用类型参数代替抽象类型是不可能的。
自身类型
自身类型是一种声明一种特质必须被混入到另一种特质的方式,尽管它并没有直接地扩展它。这使得依赖项的成员可以不用导入。
自身类型是小写 this
或别名 this
的另一个标识符的类型的一种方式。语法看起来像正常的函数语法,但表示完全不同的语法。
要在一个特质中使用自我类型,编写一个标识符,将另一个特质混入在一起,以及一个 =>
(例如 someIdentifier: SomeOtherTrait =>
)。
trait User {
def username: String
}
trait Tweeter {
this: User => // reassign this
def tweet(tweetText: String) = println(s"$username: $tweetText")
}
class VerifiedTweeter(val username_ : String) extends Tweeter with User { // We mixin User because Tweeter required it
def username = s"real $username_"
}
val realBeyoncé = new VerifiedTweeter("Beyoncé")
realBeyoncé.tweet("Just spilled my glass of lemonade") // prints "real Beyoncé: Just spilled my glass of lemonade"
因为我们说 this: User =>
在 trait Tweeter
,现在变量 username
适用于 tweet
方法。这也意味着 VerofoedTweeter
扩展了 Tweeter
,它还必须混入 User
(使用 with User
)。
复合类型
有时,有必要表示对象的类型是其他类型的子类型。在Scala中,这可以通过复合类型 的帮助来表示,这些类型是对象类型的交集。
假设我们有两个特质 Cloneable
和 Resetable
:
trait Cloneable extends java.lang.Cloneable {
override def clone(): Cloneable = {
super.clone().asInstanceOf[Cloneable]
}
}
trait Resetable {
def reset: Unit
}
现在假设我们要写一个函数 cloneAndReset
,它取出一个对象,克隆它并重新设置原始对象:
def cloneAndReset(obj: ?): Cloneable = {
val cloned = obj.clone()
obj.reset
cloned
}
问题是,参数 obj
的类型是什么。如果它是 Cloneable
则对象可以被克隆但不可以被重置。如果它是 Resetable
我们可以重置对象但却无法进行克隆操作。为了避免这种情况下的类型转换,我们可以指定类型 obj
同时是 Cloneable
和 Resetable
。这种复合类型在Scala中是这样写的:Cloneable with Resetable
。
这是更新的函数:
def cloneAndReset(obj: Cloneable with Resetable): Cloneable = {
//...
}
复合类型可以由几个对象类型组成,它们可能有一个单独的细化,可以用来缩小现有对象成员的签名。一般的形式是:A with B with C ... { refinement }
在抽象类型中给出了细化的例子。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。